<template>
	<div class="container">
		<el-row :gutter="20">
			<el-col :span="6">
				<div class="personal-operate-container">
					<ul v-for="(item, index) in roomUserList" :key="index">
						<el-tag 
                            @click="getStats"
                            size="mini"
                            type="success"
                        >{{ `用户${item.nickname}` }}</el-tag>
						<el-tag 
                            v-if="userInfo.userId === item.userId" 
                            @click="changeBitRate" 
                            type="danger" 
                            size="mini"
                        >增加比特率</el-tag>
						<el-button 
                            v-if="userInfo.userId !== item.userId" 
                            @click="call(item)" 
                            size="mini" 
                            type="primary"
                        >通话</el-button>
						<el-button 
                            v-if="userInfo.userId === item.userId" 
                            @click="changeVideoStatus"
                            size="mini" 
                            type="danger"
                        >切换</el-button>
					</ul>
				</div>
			</el-col>
			<el-col :span="18">
				<el-row>
					<div class="message-operate-container">
						<el-form  :model="formInline" label-width="250px">
                            <el-form-item label="发送消息">
                                <el-input v-model="formInline.rtcmessage"  placeholder="消息"></el-input>
                            </el-form-item>
                            <el-form-item label="远端消息">
                                <div>{{ formInline.rtcmessageRes }}</div>
                            </el-form-item>
                            <el-form-item>
                                <el-button @click="sendMessageUserRtcChannel" type="primary">点击发送</el-button>
                            </el-form-item>
						</el-form>
					</div>
				</el-row>
				<el-row>
					<div class="video-element">
						<video @click="getStreamInfo('localVideoRef')" class="local-video" ref="localVideoRef" autoplay controls muted></video>
						<video @click="getStreamInfo('remoteVideoRef')" class="remote-video" ref="remoteVideoRef" autoplay controls muted></video>
					</div>
				</el-row>
			</el-col>
		</el-row>
	</div>
</template>

<script>
    import { io } from 'socket.io-client'
	export default {
		name:'One2One',
		data(){
			return{
                peerConnection: window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection,
                userInfo:{},
                serverSocketUrl: 'ws://127.0.0.1:18080',
				linkSocket:undefined,
                channel: null,
				rtcPcParams:{
                    iceServers: [
                        // 谷歌的公共服务
                        { url: "stun:stun.l.google.com:19302"}
                    ]
				},
				roomUserList:[],
                // 用户信息
				formInline:{
					rtcmessage: undefined,
					rtcmessageRes: undefined,
				},
				localRtcPc:undefined,
				rtcmessage:undefined,
                // 发送的媒体
				mapSender:[],
			}
		},
		created() {
            const userId = this.getUrlParams("userId")
            const roomId = this.getUrlParams("roomId")
            const nickname = this.getUrlParams("nickname")
			if (userId) {
				this.initSignalService(userId, roomId, nickname)
			}
		},
		methods:{
            handleMediaError(error) {
                console.error('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);
            },
            getUrlParams(queryName) {
                let url = window.location.href
                let query = decodeURI(url.split('?')[1]);
                let vars = query.split("&");
                for (let i = 0; i < vars.length; i++) {
                    let pair = vars[i].split("=");
                    if (pair[0] === queryName) {
                        return pair[1];
                    }
                }
                return null;
            },
			async setLocalDomVideoStream(domId, newStream) {
                this.$nextTick(() => {
                    let video = this.$refs[domId]
                    let stream = video.srcObject
                    if (stream) {
                        stream.getAudioTracks().forEach( e=>{
                            stream.removeTrack(e)
                        })
                        stream.getVideoTracks().forEach(e=>{
                            stream.removeTrack(e)
                        })
                    }
                    video.srcObject = newStream
                    video.muted = true
                })
			},
			setRemoteDomVideoStream(domId, track) {
                this.$nextTick(() => {
                    let video = this.$refs[domId]
                    let stream = video.srcObject
                    if (stream) {
                        stream.addTrack(track)
                    } else {
                        let newStream = new MediaStream()
                        newStream.addTrack(track)
                        video.srcObject = newStream
                        video.muted = true
                    }
                })
			},
			initSignalService(userId, roomId, nickname) {
				this.userInfo = {
					userId: userId,
					roomId: roomId,
					nickname: nickname
				}
				this.linkSocket = io(this.serverSocketUrl, {
                    // 最大重连延迟时间
					reconnectionDelayMax: 10000,
                    // 传输方式
					transports: ["websocket"],
					query: {
					  "userId": userId,
					  "roomId": roomId,
					  "nickname": nickname
					}
				});
				this.linkSocket.on("connect", (e) => {
                    console.log('socket success => ', {
					  "userId": userId,
					  "roomId": roomId,
					  "nickname": nickname
					})
                })
				this.linkSocket.on("roomUserList", (e) => {
                    this.roomUserList = typeof e === 'object' ? Object.values(e).map(item => JSON.parse(item)) : []
					console.log("roomUserList", this.roomUserList)
				})
				this.linkSocket.on("msg", async (e) => {
					console.log("receive", e)
                    const type = e && e.type
                    const userId = e && e.userId
                    const offer = e && e.offer
                    const answer = e && e.answer
					if (type === 'join' || type === 'leave') {
						setTimeout(() => {
							const params = { "roomId": this.getUrlParams('roomId') }
							this.linkSocket.emit('roomUserList', params)
						},1000)
					}
					if (type === 'call') {
						await this.onCall(e)
					}
					if (type === 'offer') {
						await this.onRemoteOffer(userId, offer)
					}
					if (type === 'answer') {
						await this.onRemoteAnswer(userId, answer)
					}
					if (type === 'candidate') {
						this.localRtcPc.addIceCandidate(e.data.candidate)
					}
				})
				this.linkSocket.on("error", (e) => {
					console.log("socket error", e)
				})
			},
			async getUserMedia(constraints) {
			    return await navigator.mediaDevices.getUserMedia(constraints).catch(this.handleMediaError)
			},
			async call(item) {
                const params = {
					"userId": this.getUrlParams('userId'),
                    "targetUid": item.userId
                }

                const { userId: sendUserId, targetUid: receiveUserId } = params

                this.initSendClientInfo(sendUserId, receiveUserId)
				this.linkSocket.emit('call', params)
			},
			async onCall(e) {
				console.log("远程呼叫：",e)
                const { targetUid: receiveUserId, userId: sendUserId } = e.data
				await this.initReceiveClientInfo(receiveUserId, sendUserId)
			},
            async initSendClientInfo(sendUserId, receiveUserId) {
				this.mapSender = []

                // 初始化本地P2P实例
                const PeerConnection = this.peerConnection
				this.localRtcPc = new PeerConnection()

                // 存储本地媒体流
                let localStream = await this.getUserMedia({ audio: true, video: true })
				for (const track of localStream.getTracks()) {
				    this.mapSender.push(this.localRtcPc.addTrack(track));
                }

                // 渲染本地媒体流
				await this.setLocalDomVideoStream("localVideoRef", localStream)

                // 监听轨道、协商结果、消息通知、ICE过程
                this.onPcEvent(this.localRtcPc, sendUserId, receiveUserId)
				
                // 创建 offer
				const offer = await this.localRtcPc.createOffer({ iceRestart: true });
				
                // 设置本地描述
				await this.localRtcPc.setLocalDescription(offer)
				
                // 发送 offer 给接收端
				const params = {
                    "targetUid": receiveUserId,
                    "userId": sendUserId,
                    "offer": offer
                }
				this.linkSocket.emit("offer", params)
			},
			async initReceiveClientInfo(receiveUserId, sendUserId) {
                // 初始化本地P2P实例
                const PeerConnection = this.peerConnection
				this.localRtcPc = new PeerConnection()
				
                // 本地P2P实例添加媒体流
				let localStream = await this.getUserMedia({ audio: true, video: true })
				for (const track of localStream.getTracks()) {
				    this.localRtcPc.addTrack(track);
				}
                
                // 渲染本地媒体流
				await this.setLocalDomVideoStream("localVideoRef", localStream)
				
                // 监听轨道、协商结果、消息通知、ICE过程
				this.onPcEvent(this.localRtcPc, receiveUserId, sendUserId)
			},
			onPcEvent(pc, sendUserId, receiveUserId) {
                // 根据 p2p 实例创建消息通道
                this.channel = pc.createDataChannel("chat");

                // 监听轨道
                pc.ontrack = function(e) {
					console.log('监听轨道 => ', e)
					this.setRemoteDomVideoStream("remoteVideoRef", e.track)
				};

                // 监听音频格式协商
				pc.onnegotiationneeded = function(e) {
					console.log("监听协商 => ", e)
				}

                // 监听消息通道
				pc.ondatachannel = (e) => {
                    console.log('监听消息通道已创建');
                    e.channel.onopen = () => {
                        console.log('消息通道已打开');
                    };
                    e.channel.onmessage = (data) => {
                        console.log('消息通道返回消息 => ', data);
                        this.formInline.rtcmessageRes = data.data
                    };
                    e.channel.onclose = () => {
                        console.log('消息通道已关闭')
                    };
				};

                // 监听网络协商
				pc.onicecandidate = (e) => {
                    if (e.candidate) {
                        const params = {
                            "targetUid": receiveUserId,
                            "userId": sendUserId,
                            "candidate" :e.candidate
                        }
                        this.linkSocket.emit('candidate', params)
                    } else {
                        console.log("监听此次协商中, 没有更多候选了")
                    }
				}
			},
			sendMessageUserRtcChannel() {
				if(!this.channel){
					this.$message.error("请先建立webrtc连接")
				}
				this.channel.send(this.formInline.rtcmessage)
				this.formInline.rtcmessage = undefined
			},
			async onRemoteOffer(fromUid, offer) {
				this.localRtcPc.setRemoteDescription(offer)
				const answer = await this.localRtcPc.createAnswer();
				await this.localRtcPc.setLocalDescription(answer);
				const params = {
					"targetUid": fromUid,
					"userId": this.getUrlParams("userId"),
					"answer": answer
				}
				this.linkSocket.emit("answer", params)
			},
			async onRemoteAnswer(fromUid, answer) {
				await this.localRtcPc.setRemoteDescription(answer);
			},
			sendMsgToOne(event, params) {
				
			},
			changeBitRate() {
				console.log(this.localRtcPc);
				const senders = this.localRtcPc.getSenders();
				const send = senders.find((s) => s.track.kind === 'video')
				const parameters = send.getParameters();
				parameters.encodings[0].maxBitrate = 1 * 1000 * 1024;
				send.setParameters(parameters);
			},
			changeVideoStatus() {
				const senders = this.localRtcPc.getSenders();
				const send = senders.find((s) => s.track.kind === 'video')
				send.track.enabled = !send.track.enabled //控制视频显示与否
			},
			async getShareMedia() {
			    const constraints = {
			        video:{ width:1920, height:1080 },
					audio:true
			    };
			    if (window.stream) {
			        window.stream.getTracks().forEach(track => {
			            track.stop();
			        });
			    }
			    return await navigator.mediaDevices.getDisplayMedia(constraints).catch(this.handleMediaError);
			},
			getStreamInfo(domId) {
				this.$nextTick(() => {
					const videoElement = this.$refs[domId]
					console.log(videoElement.srcObject)
				})
			},
			getStats(){
				const senders = this.localRtcPc.getSenders();
				const send = senders.find((s) => s.track.kind === 'video')
				console.log('获取比特率 => ', send.getParameters().encodings);
				// 存储上一次统计结果
				let lastResultForStats;
				setInterval(() => {
					this.localRtcPc.getStats().then(res => {
						res.forEach(report => {
							let bytes;
							let headerBytes;
							let packets;
							// 出口宽带 outbound-rtp  入口宽带 inbound-rtp
							if (report.type === 'outbound-rtp' && report.kind ==='video') {
								const now = report.timestamp;
								bytes = report.bytesSent;
								headerBytes = report.headerBytesSent;
								packets = report.packetsSent;	

								if (lastResultForStats && lastResultForStats.has(report.id)) {
									// 存在上一次结果, 则拿最新结果减去上一次结果
									let bf = bytes - lastResultForStats.get(report.id).bytesSent
									let hbf = headerBytes - lastResultForStats.get(report.id).headerBytesSent
									let pacf = packets - lastResultForStats.get(report.id).packetsSent
									let t = now - lastResultForStats.get(report.id).timestamp

									// 计算比特率
									const bitrate = Math.floor(8 * bf/t);
									const headerrate = Math.floor(8 * hbf/t);
									const packetrate = Math.floor(1000 * pacf/t);
									console.log(`Bitrate ${bitrate} kbps, overhead ${headerrate} kbps, ${packetrate} packets/second`);
								}
							}
						})

						// 存储最新结果
						lastResultForStats = res
					})
				},4000)
				
			}
		}
	}
</script>

<style scoped lang="less">
    .container {
        width: 98%;
        height: 98vh;
        margin-top: 30px;
        .personal-operate-container {
            width: 100%;
            height: 800px;
        }
        .message-operate-container {
            width: 800px;
            height: 200px;
            display: flex;
            flex-direction: row;
            align-items: center;
        }
        .video-element {
            display: flex;
            flex-direction: row;
            justify-content: flex-start;
        }
        .local-video {
            width: 300px;
            height: 200px;
        }
        .remote-video {
            width: 300px;
            height: 200px;
        }
    }
</style>